package app_kvEcs;

import utils.CommonUtil;

/**
 * @author Cipri_L
 * Maintains a list with the server nodes in order of the allocated memory space the server node should serve.
 */
public class MemorySpace {
	
	private ServerNode first = null;
	public static final String START = "00000000000000000000000000000000";
	public static final String FINAL = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";
	
	public MemorySpace() {
	}

	public ServerNode firstNode(){
		return first;
	}
	
	/**
	 * @param newNode
	 * @return rellocation message <source_ip:source_port:start_range:end_range:destination_ip:destination_port>#<...> 
	 */
	public String addNode(ServerNode newNode){
		if(newNode == null)
			return "";
		newNode.setAllocated(true);
		//add first node
		if(first == null){
			first = newNode;
			newNode.setStartRange(START);
			newNode.setEndRange(FINAL);
			return "";
		}
		
		String rellocationMessage = "";
		//add in the middle
		String position = newNode.getPosition();
		ServerNode node = first;
		ServerNode lastNode = first;
		do{
			int compare = CommonUtil.byteStringCompare(position, node.getPosition());
			if(compare < 0){				
				return addBefore(node, newNode);
			}
			lastNode = node;
			node = node.next();
		}while(node != null);
		
		newNode.setPrevious(lastNode);
		lastNode.setNext(newNode);
		//add at the end of the range
		newNode.setEndRange(newNode.getPosition());		
		String newStart = CommonUtil.byteStringAddition(lastNode.getPosition());
		newNode.setStartRange(newStart);
		String newEnd = CommonUtil.byteStringAddition(newNode.getPosition());
		if(first.extendedRange() && !newEnd.equals(FINAL)){
			first.setStartRangeExtended(newEnd);
		}else{
			if(first.endRange().equals(FINAL)){
				first.setExtendedRange(true);
				first.setStartRangeExtended(newEnd);
				first.setEndRangeExtended(FINAL);
				first.setEndRange(first.getPosition());
			}
		}
		
		rellocationMessage += first.ip()+":"+first.port()+":"+newNode.startRange()+":"+newNode.endRange()+":"+newNode.ip()+":"+newNode.port();
		return rellocationMessage;
	}

	private String addBefore(ServerNode node, ServerNode beforeNode) {
		String rellocationMessage = "";
		beforeNode.setNext(node);				
		if(node.previous()!=null){
			beforeNode.setPrevious(node.previous());
			node.previous().setNext(beforeNode);
		}else{
			//before the first node
			first = beforeNode;
			if(node.extendedRange()){
				beforeNode.setExtendedRange(true);
				beforeNode.setStartRangeExtended(node.startRangeExtended());
				beforeNode.setEndRangeExtended(node.endRangeExtended());
				node.setExtendedRange(false);
			}else{
				beforeNode.setExtendedRange(true);
				String newStart = CommonUtil.byteStringAddition(node.getPosition());
				beforeNode.setStartRangeExtended(newStart);
				beforeNode.setEndRangeExtended(FINAL);
				node.setEndRange(node.getPosition());
			}
		}				
		node.setPrevious(beforeNode);
		
		beforeNode.setStartRange(node.startRange());
		String position = beforeNode.getPosition();
		beforeNode.setEndRange(position);
		
		String startRange = CommonUtil.byteStringAddition(position);
		if(startRange.length() > 32)
			startRange = MemorySpace.FINAL;
		node.setStartRange(startRange);	
		
		rellocationMessage += node.ip()+":"+node.port()+":"+beforeNode.startRange()+":"+beforeNode.endRange()+":"+beforeNode.ip()+":"+beforeNode.port();
		if(beforeNode.extendedRange()){
			rellocationMessage += "#";
			rellocationMessage += node.ip()+":"+node.port()+":"+beforeNode.startRangeExtended()+":"+beforeNode.endRangeExtended()+":"+beforeNode.ip()+":"+beforeNode.port();
		}
		return rellocationMessage;
	}
	
	/**
	 * @param toDelete
	 * @return rellocation message <source_ip:source_port:start_range:end_range:destination_ip:destination_port>#<...>
	 */
	public String removeNode(ServerNode toDelete){
		if(toDelete == null)
			return "";
		String rellocationMessage = "";
		toDelete.setAllocated(false);
		ServerNode node = first;
		while(node!=null){
			if(node.equals(toDelete)){
				if(node.extendedRange()){
					if(node.next()!=null){
						//only the first node can have extended range
						node.next().setExtendedRange(true);
						node.next().setStartRangeExtended(node.startRangeExtended());
						node.next().setEndRangeExtended(node.endRangeExtended());
						node.next().setStartRange(node.startRange());
						first = node.next();
						rellocationMessage += node.ip()+":"+node.port()+":"+node.startRange()+":"+node.endRange()+":"+first.ip()+":"+first.port();
						rellocationMessage += "#";
						rellocationMessage += node.ip()+":"+node.port()+":"+node.startRangeExtended()+":"+node.endRangeExtended()+":"+first.ip()+":"+first.port();
						return rellocationMessage ;
					}else{
						//it is the only node
						first = null;
						this.clear();
						return rellocationMessage ;
					}
				}else{
					if(node.next()!=null){
						node.next().setStartRange(node.startRange());
						node.next().setPrevious(node.previous());
						if(node.previous()!=null){
							node.previous().setNext(node.next());
						}
						rellocationMessage += node.ip()+":"+node.port()+":"+node.startRange()+":"+node.endRange()+":"+node.next().ip()+":"+node.next().port();
						return rellocationMessage ;
					}else{
						//it is the last node
						if(node.equals(first)){
							first = null;
							this.clear();
							return rellocationMessage ;
						}else {
							if(!first.extendedRange()){
								//it is the last node
								first.setExtendedRange(true);
								first.setEndRangeExtended(FINAL);
								first.setStartRangeExtended(node.startRange());
							} else {
								first.setEndRangeExtended(FINAL);
								first.setStartRangeExtended(node.startRange());
							}
							node.previous().setNext(null);
							rellocationMessage += node.ip()+":"+node.port()+":"+node.startRange()+":"+node.endRange()+":"+first.ip()+":"+first.port();
							return rellocationMessage ;
						}
					}
				}
			}
			node = node.next();
		}
		return rellocationMessage;
	}
	
	
	public String removeNode(String nodeName){
		ServerNode node = first ;
		while(node!=null){
			if(node.name().equals(nodeName)){
				return this.removeNode(node);
			}
			node = node.next();
		}
		return "";
	}
	
	public ServerNode getServerNode(String position){
		ServerNode node = first;		
		while(node!=null){
			if(position.equals(node.getPosition()))
				return node;
			node = node.next();
		}
		return null;
	}
	
	/**
	 * Clear memory space
	 */
	public void clear(){
		ServerNode node = first;		
		while(node!=null){
			node = node.next();
			first.setAllocated(false);
			first.setPrevious(null);
			first.setNext(null);
			first.setStartRange("");
			first.setEndRange("");
			first.setExtendedRange(false);
			first.setEndRangeExtended("");
			first.setStartRangeExtended("");
			first = node;
		}
		this.first = null;
	}
	
	public String toString(){
		String result = "";
		
		ServerNode node = this.first;
		while(node!=null){
			result += node.info() + " |\n";
			node = node.next();
		}
		return result;
	}
	
	/**
	 * here METADATA is a list of server nodes in the following format: 
	 * server_ip1;server_port1;range_start1;range_end1#server_ip2;server_port2;range_start2;range_end2;
	 * A server determines its own metadata by simple ip-port check outs
	 * @return
	 */
	public String getMetadata(){
		String result = "";
		ServerNode node = this.first;
		while(node!=null){
			result += node.ip()+";"+node.port()+";"+node.startRange()+";"+node.endRange()+";"; 
			result += "#";
			if(node.extendedRange()){
				result += node.ip()+";"+node.port()+";"+node.startRangeExtended()+";"+node.endRangeExtended()+";"; 
				result += "#";
			}
			node = node.next();
		}
		if(result.length()>=2) {
			result = result.substring(0, result.length()-2);
		}
		return result;
	}
	
}
